/*
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

package com.facebook.drawee.drawable;

import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;

import com.facebook.common.internal.Preconditions;

/**
 * A Drawable that contains an array of other Drawables (layers). These are drawn in array order,
 * so the element with the largest index will be drawn on top.
 * <p/>
 * <p>Similar to android's LayerDrawable but it doesn't support adding/removing layers dynamically.
 */
public class ArrayDrawable extends Drawable
        implements Drawable.Callback, TransformCallback, TransformAwareDrawable {

    private TransformCallback mTransformCallback;

    private final DrawableProperties mDrawableProperties = new DrawableProperties();

    // layers
    private final Drawable[] mLayers;

    // temp rect to avoid allocations
    private final Rect mTmpRect = new Rect();

    // Whether the drawable is stateful or not
    private boolean mIsStateful = false;
    private boolean mIsStatefulCalculated = false;

    private boolean mIsMutated = false;

    /**
     * Constructs a new layer drawable.
     *
     * @param layers the layers that this drawable displays
     */
    public ArrayDrawable(Drawable[] layers) {
        Preconditions.checkNotNull(layers);
        mLayers = layers;
        for (int i = 0; i < mLayers.length; i++) {
            DrawableUtils.setCallbacks(mLayers[i], this, this);
        }
    }

    /**
     * Gets the number of layers.
     *
     * @return number of layers
     */
    public int getNumberOfLayers() {
        return mLayers.length;
    }

    /**
     * Gets the drawable at the specified index.
     *
     * @param index index of drawable to get
     * @return drawable at the specified index
     */
    public Drawable getDrawable(int index) {
        return mLayers[index];
    }

    /**
     * Sets a new drawable at the specified index.
     */
    public void setDrawable(int index, Drawable drawable) {
        Preconditions.checkArgument(index >= 0);
        Preconditions.checkArgument(index < mLayers.length);
        if (drawable != mLayers[index]) {
            if (mIsMutated) {
                drawable = drawable.mutate();
            }
            DrawableUtils.setCallbacks(mLayers[index], null, null);
            DrawableUtils.setCallbacks(drawable, null, null);
            DrawableUtils.setDrawableProperties(drawable, mDrawableProperties);
            DrawableUtils.copyProperties(drawable, mLayers[index]);
            DrawableUtils.setCallbacks(drawable, this, this);
            mIsStatefulCalculated = false;
            mLayers[index] = drawable;
            invalidateSelf();
        }
    }


    @Override
    public int getIntrinsicWidth() {
        int width = 0;
        for (int i = 0; i < mLayers.length; i++) {
            width = Math.max(width, mLayers[i].getIntrinsicWidth());
        }
        return width;
    }

    @Override
    public int getIntrinsicHeight() {
        int height = 0;
        for (int i = 0; i < mLayers.length; i++) {
            height = Math.max(height, mLayers[i].getIntrinsicHeight());
        }
        return height;
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        for (int i = 0; i < mLayers.length; i++) {
            mLayers[i].setBounds(bounds);
        }
    }

    @Override
    public boolean isStateful() {
        if (!mIsStatefulCalculated) {
            mIsStateful = false;
            for (int i = 0; i < mLayers.length; i++) {
                mIsStateful |= mLayers[i].isStateful();
            }
            mIsStatefulCalculated = true;
        }
        return mIsStateful;
    }

    @Override
    protected boolean onStateChange(int[] state) {
        boolean stateChanged = false;
        for (int i = 0; i < mLayers.length; i++) {
            if (mLayers[i].setState(state)) {
                stateChanged = true;
            }
        }
        return stateChanged;
    }

    @Override
    protected boolean onLevelChange(int level) {
        boolean levelChanged = false;
        for (int i = 0; i < mLayers.length; i++) {
            if (mLayers[i].setLevel(level)) {
                levelChanged = true;
            }
        }
        return levelChanged;
    }

    @Override
    public void draw(Canvas canvas) {
        for (int i = 0; i < mLayers.length; i++) {
            mLayers[i].draw(canvas);
        }
    }

    @Override
    public boolean getPadding(Rect padding) {
        padding.left = 0;
        padding.top = 0;
        padding.right = 0;
        padding.bottom = 0;
        final Rect rect = mTmpRect;
        for (int i = 0; i < mLayers.length; i++) {
            mLayers[i].getPadding(rect);
            padding.left = Math.max(padding.left, rect.left);
            padding.top = Math.max(padding.top, rect.top);
            padding.right = Math.max(padding.right, rect.right);
            padding.bottom = Math.max(padding.bottom, rect.bottom);
        }
        return true;
    }

    @Override
    public Drawable mutate() {
        for (int i = 0; i < mLayers.length; i++) {
            mLayers[i].mutate();
        }
        mIsMutated = true;
        return this;
    }

    @Override
    public int getOpacity() {
        if (mLayers.length == 0) {
            return PixelFormat.TRANSPARENT;
        }
        int opacity = mLayers[0].getOpacity();
        for (int i = 1; i < mLayers.length; i++) {
            opacity = Drawable.resolveOpacity(opacity, mLayers[i].getOpacity());
        }
        return opacity;
    }

    @Override
    public void setAlpha(int alpha) {
        mDrawableProperties.setAlpha(alpha);
        for (int i = 0; i < mLayers.length; i++) {
            mLayers[i].setAlpha(alpha);
        }
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        mDrawableProperties.setColorFilter(colorFilter);
        for (int i = 0; i < mLayers.length; i++) {
            mLayers[i].setColorFilter(colorFilter);
        }
    }

    @Override
    public void setDither(boolean dither) {
        mDrawableProperties.setDither(dither);
        for (int i = 0; i < mLayers.length; i++) {
            mLayers[i].setDither(dither);
        }
    }

    @Override
    public void setFilterBitmap(boolean filterBitmap) {
        mDrawableProperties.setFilterBitmap(filterBitmap);
        for (int i = 0; i < mLayers.length; i++) {
            mLayers[i].setFilterBitmap(filterBitmap);
        }
    }

    @Override
    public boolean setVisible(boolean visible, boolean restart) {
        boolean changed = super.setVisible(visible, restart);
        for (int i = 0; i < mLayers.length; i++) {
            mLayers[i].setVisible(visible, restart);
        }
        return changed;
    }

    /**
     * Drawable.Callback methods
     */

    @Override
    public void invalidateDrawable(Drawable who) {
        invalidateSelf();
    }

    @Override
    public void scheduleDrawable(Drawable who, Runnable what, long when) {
        scheduleSelf(what, when);
    }

    @Override
    public void unscheduleDrawable(Drawable who, Runnable what) {
        unscheduleSelf(what);
    }

    /**
     * TransformationCallbackSetter method
     */
    @Override
    public void setTransformCallback(TransformCallback transformCallback) {
        mTransformCallback = transformCallback;
    }

    /**
     * TransformationCallback methods
     */

    @Override
    public void getTransform(Matrix transform) {
        if (mTransformCallback != null) {
            mTransformCallback.getTransform(transform);
        } else {
            transform.reset();
        }
    }

    @Override
    public void getRootBounds(RectF bounds) {
        if (mTransformCallback != null) {
            mTransformCallback.getRootBounds(bounds);
        } else {
            bounds.set(getBounds());
        }
    }
}
